/*
 * Luke Valenta
 * lvalenta
 * 112113230
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>

#include "bencode.h"
#include "sha1.h"

#define DEFAULT_PORT    80 /* default http port */
#define PEER_PORT       6881 /* This port number doesn't matter */
#define MAX_LEN         100000 /* A buffer this size hopefully won't overflow */
#define MAX_URL_LEN     3000

#define DEBUG 0
#define VERBOSE 1

/* Function prototype */
char *get_sha1_hash(sha1_byte_t *, int length);
char *get_peer_id();
int get_connected_socket(char *host, short port);
benc_val_t *readTorrentFile(char *);
void process_response(char *response, int response_len);
char *url_encode(unsigned char *, int);

int main(int argc, char ** argv) {
    /* Arrays to hold the messages when they are sent and received */
    char request[MAX_LEN], response[MAX_LEN];
    /* Fields of the HTTP requests */
    char *info_hash, *peer_id, path[MAX_URL_LEN], host[MAX_URL_LEN];
    short tracker_port;
    /* Temporary variables */
    char *info, *announce;
    int ret, len, response_len, idx, ii;
    benc_val_t *val, *list_val, *announce_val;
    /* Other variables */
    int sockfd;

    /* Check command line arguments */
    if(argc != 2) {
        printf("USAGE: %s <torrent file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
   
    val = readTorrentFile(argv[1]);

    announce_val = tr_bencDictFind(val, "announce");

    /* If the 'announce' field is not present for some reason, use
     * the first tracker URL in the 'announce-list' list of lists */
    if(announce_val == NULL) {
        /* Check if the announce-list key is present */
        if((list_val = tr_bencDictFind(val, "announce-list")) != NULL) {
            if(DEBUG) printf("announce-list is present\n");
            announce_val = (list_val->val.l.vals)->val.l.vals; 
            if(DEBUG) tr_bencPrint(list_val);
        }
    }

    announce = malloc(10*announce_val->val.i);

    idx = 0;
    for( ii = 0; announce_val->val.s.i > ii; ii++ )
    {
        if( '\\' == announce_val->val.s.s[ii] )
        {
            announce[idx++] = '\\';
            announce[idx++] = '\\';
        }
        else if( isprint( announce_val->val.s.s[ii] ) )
        {
            announce[idx++] = announce_val->val.s.s[ii];
        }
        else
        {
            idx += sprintf(announce+idx, "\\x%02x", announce_val->val.s.s[ii]);
        }
    }

    announce[announce_val->val.i] = 0;
    if(DEBUG) printf("%s\n", announce);

    ret = sscanf(announce, "%*[^:]://%2999[^/: ]:%hd%2999s", host, &tracker_port, path);
    if (ret == 1) {
        sscanf(announce, "%*[^:]://%*2999[^/ ]%2999s", path);
        tracker_port = DEFAULT_PORT;
    }
    if(DEBUG) printf("HOST: %s\n", host);
    if(DEBUG) printf("PORT: %d\n", tracker_port);


    /* Get url-encoded peer id */
    /* IMPORTANT: Remember to free peer id when it is no longer needed */
    peer_id = get_peer_id();
    if(DEBUG) printf("PEER_ID: %s\n", peer_id);

    /* Get url-encoded sha1 hash of the info field from the torrent file */
    info = tr_bencSaveMalloc(tr_bencDictFind(val, "info"), &len);
    info_hash = get_sha1_hash((sha1_byte_t *)info, len);
    if(DEBUG) printf("INFO HASH: %s\n", info_hash);


    /* Create http GET request */ 
    len = snprintf(request, MAX_LEN,
            "GET %s?peer_id=%s&info_hash=%s&compact=1&left=1&uploaded=0&downloaded=0&port=%d&event=started&numwant=50&no_peer_id=1 HTTP/1.1\r\n"
            "Host: %s\r\n"
            "Accept: */*\r\n"
            "\r\n", path, peer_id, info_hash, PEER_PORT, host);

    /* We are done with this allocated memory */
    tr_bencFree(val);

    if(DEBUG) printf("%s\n", request);

    /* Create socket */
    sockfd = get_connected_socket(host, tracker_port);
    /* Send request to tracker */
    if(send(sockfd, request, len, 0) < 0) {
        fprintf(stderr, "send() failed\n");
        exit(1);
    }

    /* Receive response from tracker */
    bzero(response, MAX_LEN);
    response_len = 0;
    while((len = recv(sockfd, response + response_len, MAX_LEN - response_len, 0)) > 0) {
        response_len += len;
    }
    /*
    response_len = recv(sockfd, response, MAX_LEN, MSG_WAITALL);
    */

    /* We are done with this connection now */
    close(sockfd);

    if(response_len < 0) {
        fprintf(stderr, "recv() failed\n");
        exit(1);
    } else if(response_len == 0) {
        /* Connection was closed by the tracker */
        fprintf(stderr, "Connection closed by tracker\n");
    }

    /* Process the HTTP response received from tracker and print out peers list */
    process_response(response, response_len);

    /* Close connection with tracker */
    len = snprintf(request, MAX_LEN,
            "GET %s?peer_id=%s&info_hash=%s&compact=1&left=1&uploaded=0&downloaded=0&port=%d&event=stopped&numwant=50&no_peer_id=1 HTTP/1.1\r\n"
            "Host: %s\r\n"
            "Accept: */*\r\n"
            "Connection: close\r\n"
            "\r\n", path, peer_id, info_hash, PEER_PORT, host);

    if(DEBUG) printf("%s\n", request);

    /* Create a new connection with the tracker to send close request */
    sockfd = get_connected_socket(host, tracker_port);
    /* Send close request to tracker */
    if(send(sockfd, request, len, 0) < 0) {
        fprintf(stderr, "send() failed\n");
        exit(1);
    }
    close(sockfd);

    free(info_hash);
    free(peer_id);
    close(sockfd);
    return 0;
}

void process_response(char *response, int response_len) {
    char *pch, *end;
    int len;
    benc_val_t *val, *benc_peers;
    char *peers_str, *ptr;
    char str_ip[17]; /* xxx.xxx.xxx.xxx */
    int peers_length, peers_str_len, idx, peer_ip;
    unsigned short peer_port;

    /* Find the starting index and length of the tracker response */
    pch = strtok(response, "\r\n");
    len = 0;
    while(pch != NULL) {
        if(pch[0] == 'd') {
            break;
        }
        pch = strtok(NULL, "\r\n");
    }
    len = response_len - (int)(pch - response);

    if(DEBUG) printf("pch:\t%s\nlen:\t%d\n", pch, len);

    /* Allocate space for benc_val_t structure */
    val = malloc(sizeof(benc_val_t));

    if(tr_bencLoad(pch, len, val, &end) != 0) {
        fprintf(stderr, "Unable to parse response\n");
        return;
    }
    if(DEBUG) tr_bencPrint(val);
    
    /* Process response from tracker */
    benc_peers = tr_bencDictFind(val, "peers");
    if(benc_peers == NULL) {
        if(VERBOSE) printf("No peers found in tracker response\n");
    } else if(benc_peers->type == TYPE_STR) {
        /* Process string of peers */
        peers_str = tr_bencSaveMalloc(benc_peers, &peers_length);
        sscanf(peers_str, "%d:", &peers_str_len);
        /* Skip past the '%d:' part of the string */
        ptr = strstr(peers_str, ":");
        ++ptr;
        for(idx = 0; idx < peers_str_len/6; ++idx) {
            peer_ip = ntohl(*((uint32_t *) ptr));
            ptr+=4;
            peer_port = ntohs(*((uint16_t *) ptr));
            ptr+=2;
            sprintf(str_ip, "%d.%d.%d.%d", (peer_ip >> 24) & 0xFF, (peer_ip >> 16) & 0xFF, (peer_ip >> 8) & 0xFF, (peer_ip) & 0xFF);
            printf("peer%d: %s %u\n", idx, str_ip, peer_port);
        }
    } else if (benc_peers->type == TYPE_DICT) {
        /* Process dict of peers */
    } else {
        if(DEBUG) printf("Peers list is malformed\n");
    }

    /* We are done with this allocated memory */
    tr_bencFree(val);
    free(val);
}

/* Connects a socket to the given host and port, and passes back
 * the given socket and local port number as out parameters */
int get_connected_socket(char *host, short port) {
    int sockfd;
    struct hostent *hp;
    struct sockaddr_in servaddr;

    /* Get the host of the given hostname */
    hp = gethostbyname(host);
    if(hp == NULL) { 
        fprintf(stderr, "could not obtain address from %s\n", host); 
        exit(1); 
    }

    /* Create TCP socket */
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sockfd < 0) { 
        fprintf(stderr, "socket() failed\n"); 
        exit(1); 
    }

    /* Construct tracker address */
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    memcpy((void *)&servaddr.sin_addr, hp->h_addr_list[0], hp->h_length);
    servaddr.sin_port = htons(port);
    
    /* Connect to tracker */
    if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        fprintf(stderr, "connect() failed\n");
        exit(1);
    }
    return sockfd;
}

/* Read .torrent file into a benc_val_t structure */
benc_val_t *readTorrentFile(char *filename) {
    FILE *fp;
    char *bytes;
    long fsize;
    benc_val_t *val; 
    char *end;

    /* Open the .torrent file */
    fp = fopen(filename, "rb");
    if(fp == NULL) { fprintf(stderr, "failed to open file %s\n", filename); exit(1); }

    /* Get the size of the file */
    fseek(fp, 0, SEEK_END);
    fsize = ftell(fp);
    fseek(fp, 0, SEEK_SET); /* rewind */

    /* Allocate enough space to store the file contents */
    bytes = malloc(fsize);
    if(bytes == NULL) { fprintf(stderr, "malloc() failed\n"); exit(1); }

    /* Read the contents of the file into the bytes buffer */
    if (fread(bytes, fsize, 1, fp) != 1) { fprintf(stderr, "read error\n"); exit(1); }
    fclose(fp);

    /* Allocate space for benc_val_t structure */
    val = malloc(sizeof(benc_val_t));

    /* Convert the byte array to a benc_val_t structure */
    tr_bencLoad(bytes, fsize, val, &end);    

    /* We are done with this allocated memory */
    free(bytes);

    return val;
}

/* Return url-encoded 20 byte SHA1 hash of input */
char *get_sha1_hash(sha1_byte_t *input, int length) {
    sha1_byte_t *output;
    sha1_state_s hash;

    output = malloc(SHA1_OUTPUT_SIZE);

	sha1_init(&hash);
	sha1_update(&hash, input, length);
	sha1_finish(&hash, output);

    /* Remember to free this buffer when we are done with it */
    return url_encode(output, 20);
}

char *get_peer_id() {
    unsigned char *buf = malloc(20);
    int i;
    srand(time(NULL));
    for (i = 0; i < 20; i++) {
            buf[i] = rand() % 256;
    }
    return url_encode(buf,20);
}

/** See the README file for information on the author of this code **/

/* Converts an integer value to its hex character*/
char to_hex(char code) {
    static char hex[] = "0123456789abcdef";
    return hex[code & 15];
}

/* Returns a url-encoded version of buf */
/* IMPORTANT: be sure to free() the returned buffer after use */
char *url_encode(unsigned char *buf, int length) {
    char *new = malloc(length*3+1), *pnew = new;
    int i;
    for(i = 0; i < length; ++i) {
        if(isalnum(buf[i]) || buf[i] == '-' || buf[i] == '_' || buf[i] == '.' || buf[i] == '~')
            *pnew++ = buf[i];
        else if (buf[i] == ' ')
            *pnew++ = '+';
        else
            *pnew++ = '%', *pnew++ = to_hex(buf[i] >> 4), *pnew++ = to_hex(buf[i] & 15);
    }
    *pnew = '\0';
    return new;
}
/** End of URL encoding/decoding **/
